package websocket;
import com.alibaba.fastjson.JSONObject;
import game.Z5CGame;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint(value = "/ws/{roomId}/{connectCamp}/{cbSize}")
public class BattleTestWS {

    private static final String CLEANJSONMESSAGE;
    private static final String EXITJSONMESSAGE;
    private static final String DRAWMESSAGE;

    static {
        JSONObject TempJson = new JSONObject();
        TempJson.put("type", "clean");
        CLEANJSONMESSAGE = TempJson.toJSONString();
        TempJson.put("type", "exit");
        EXITJSONMESSAGE = TempJson.toJSONString();
        TempJson.put("type", "draw");
        DRAWMESSAGE = TempJson.toJSONString();
    }

    public static class TestGameRoom {
        public String id;

        public BattleTestWS host;
        public BattleTestWS challenger;

        public Z5CGame.Camp hostCamp;
        public Z5CGame game;

        public HashSet<BattleTestWS> visitors;

        public int readyPlayer;

        public TestGameRoom(String roonId, BattleTestWS host, BattleTestWS challenger, Z5CGame.Camp hostCamp, int checkboardSize) {
            this.id          = roonId;
            this.host        = host;
            this.challenger  = challenger;
            this.hostCamp    = hostCamp;
            this.game        = new Z5CGame(checkboardSize);
            this.visitors    = new HashSet<>();
            this.readyPlayer = 2;
        }

        public TestGameRoom(String roonId, BattleTestWS host, Z5CGame.Camp hostCamp, int checkboardSize) {
            this(roonId, host, null, hostCamp, checkboardSize);
            this.readyPlayer = 2;
        }

        public synchronized void addVistor(BattleTestWS visitor) {
            visitors.add(visitor);
        }

        public synchronized void broadcast(String response) {
            Session s;
            if(host != null && (s = host.getSessionContext()) != null && s.isOpen()) s.getAsyncRemote().sendText(response);
            if(challenger != null && (s = challenger.getSessionContext()) != null && s.isOpen())  s.getAsyncRemote().sendText(response);
            for(BattleTestWS visitor : visitors) {
                if(visitor != null && (s = visitor.getSessionContext()) != null && s.isOpen()) s.getAsyncRemote().sendText(response);
            }
        }

        public synchronized void clearGame() {
            game.clear();
            readyPlayer = 2;
            broadcast(CLEANJSONMESSAGE);
        }

        public synchronized void sendCheckerboard(Session visitorSession) {
            JSONObject resp = new JSONObject();
            resp.put("type", "getCheckerboard");
            resp.put("siz", game.getSize());
            resp.put("data", game.getHistory());
            visitorSession.getAsyncRemote().sendText(resp.toJSONString());
        }
    }

    private static ConcurrentHashMap<String, TestGameRoom> roomIndex;
    ///////////////////////////////////////////////////////////////////
    private TestGameRoom currentRoom = null;
    private Session sessionContext   = null;
    ///////////////////////////////////////////////////////////////////

    public final Session getSessionContext() {
        return sessionContext;
    }

    public final void setSessionContext(Session sessionContext) {
        this.sessionContext = sessionContext;
    }

    public final TestGameRoom getCurrentRoom() {
        return currentRoom;
    }

    public final void setCurrentRoom(TestGameRoom currentRoom) {
        this.currentRoom = currentRoom;
    }

    static {
        roomIndex = new ConcurrentHashMap<>();
        /*
        System.out.println("initDb...");
        DbInit.init();
        */
    }

    /*
    var msg = JSON.stringify({
			type:"requestGo",
			row:i,
			col:j,
			cmp:camp
		});
    */

    @OnOpen
    public void onOpen(
        @PathParam("roomId")      String  id,
        @PathParam("connectCamp") String  camp,
        @PathParam("cbSize")      int     size,
                                  Session session
    ) throws IOException {
        sessionContext = session;
        TestGameRoom room = roomIndex.get(id);
        if(room == null) { //创建并主持房间
            if("black".equals(camp)) {
                room = new TestGameRoom(id, this, Z5CGame.Camp.black, size);
            } else if("white".equals(camp)) {
                room = new TestGameRoom(id, this, Z5CGame.Camp.white, size);
            } else { //观众不能创建房间
                sessionContext = null;
                currentRoom = null;
                session.close();
                return;
            }
            roomIndex.put(id, room);
            //System.out.println("Host joined.");
        } else { //房间已存在，且房主存在
            synchronized(room) {
                if(room.host.getSessionContext() != session) {
                    if(room.challenger == null) { //加入对局
                        room.challenger = this;
                        size = room.game.getSize();
                        configPlayerCheckerboard(room.host.getSessionContext(), size, Z5CGame.getCampName(room.hostCamp));
                        configPlayerCheckerboard(session, size, (room.hostCamp == Z5CGame.Camp.black? "white": "black"));
                        //System.out.println("Challenger joined.");
                    } else if(room.challenger.getSessionContext() != session) {
                        room.addVistor(this);//加入观众
                        size = room.game.getSize();
                        //configPlayerCheckerboard(session, size, "none");
                        room.sendCheckerboard(session);
                        //System.out.println("Visitor joined.");
                    }
                }
            }
        }
        currentRoom = room;
    }

    private synchronized void configPlayerCheckerboard(Session player, int size, String camp) {
        JSONObject response = new JSONObject();
        response.put("type", "configCheckerboard");
        response.put("siz", size);
        response.put("cmp", camp);
        player.getAsyncRemote().sendText(response.toJSONString());
    }

    /**
     * 令Client端执行落子
     * @param room    房间
     * @param request 落子请求信息
     */
    private static void doGo(TestGameRoom room, JSONObject request) {

        /*
            var msg = JSON.stringify({
			type:"requestGo",
			row:i,
			col:j,
			cmp:camp
		});
         */
        //在此处理游戏逻辑
        String requestCamp = (String)request.get("cmp");
        Z5CGame.Camp camp = Z5CGame.getCamp(requestCamp);;
        Z5CGame.Result result = room.game.go((Integer)request.get("row"), (Integer)request.get("col"), camp);

        switch(result.getResult()) {
            case accept:
                request.put("type", "go");
                String response = request.toJSONString();
                room.broadcast(response);
                break;
            case reject: break;
            case blackWin: {
                request.put("type", "blackWin");
                request.put("win", result.getWinBy());
                room.broadcast(request.toJSONString());
                room.readyPlayer = 0;
                break;
            }
            case whiteWin: {
                request.put("type", "whiteWin");
                request.put("win", result.getWinBy());
                room.broadcast(request.toJSONString());
                room.readyPlayer = 0;
                break;
            }
            case draw:
                room.broadcast(DRAWMESSAGE);
                break;
        }
    }

    private static void doRollback(TestGameRoom room, JSONObject request) {
        /* case "rollback":
         * 	Checkerboard.rollback(response.rbk[0], response.rbk[1], response.rbk[2], response.rbk[3]);
         * 	break;
         */
        Z5CGame.Camp camp = Z5CGame.getCamp((String)request.get("cmp"));
        int [] rollbackResult = room.game.rollback(camp);
        //System.out.println("doRollback: [" + rollbackResult[0] + ',' + rollbackResult[1] + ',' + rollbackResult[2] + ',' + rollbackResult[3] + ']');
        if(rollbackResult[0] != -1) {
            request.put("type", "rollback");
            request.put("rbk", rollbackResult);
            room.broadcast(request.toJSONString());
        }
    }

    public static void doConfirm(TestGameRoom room) {
        if(++room.readyPlayer == 2) {
            room.broadcast(CLEANJSONMESSAGE);
        }
    }

    @OnMessage
    public void messageDispatcher(@PathParam("roomId") String id, Session session, String msg) {

        //System.out.println(msg);
        JSONObject request  = JSONObject.parseObject(msg);
        TestGameRoom room = roomIndex.get(id);

        if(room != null) switch(request.get("type").toString()) {
            case "requestGo":
                synchronized (room) {
                    if(room.host != null && room.challenger != null && room.readyPlayer == 2) {
                        if(session != room.host.getSessionContext() && session != room.challenger.getSessionContext()) break;
                        doGo(room, request);
                    }
                }
                break;
            case "requestRollback":
                synchronized (room) {
                    doRollback(room, request);
                }
                break;
            case "playerReady":
                synchronized (room) {
                    doConfirm(room);
                }
                break;
        }
    }

    @OnMessage
    public void binaryMessage(Session session, ByteBuffer msg) {
        System.out.println("Binary message: " + msg.toString());
    }

    @OnClose
    public void onClose(Session session) throws IOException {

        if(currentRoom == null) return;

        synchronized (currentRoom) {
            if (/*currentRoom.host != null && */currentRoom.host.getSessionContext() == session) {//房主退出
                currentRoom.host = currentRoom.challenger;
                currentRoom.hostCamp = (currentRoom.hostCamp == Z5CGame.Camp.black? Z5CGame.Camp.white: Z5CGame.Camp.black);
                currentRoom.challenger = null;

                if (currentRoom.host == null) { //房主与挑战者均退出，关闭并销毁房间
                    currentRoom.broadcast(EXITJSONMESSAGE);
                    for (BattleTestWS visitor : currentRoom.visitors) {
                        visitor.currentRoom = null;
                        visitor.sessionContext.close();
                        visitor.sessionContext = null;
                    }
                    roomIndex.remove(currentRoom.id);
                } else currentRoom.clearGame();
                //System.out.println("Host exit.");
            } else if (currentRoom.challenger != null && currentRoom.challenger.getSessionContext() == session) {//挑战者退出
                currentRoom.challenger = null;
                currentRoom.clearGame();
                //System.out.println("Challenger exit.");
            } else {//观众退出
                currentRoom.visitors.remove(this);
                //System.out.println("Visitor exit.");
            }
        }
        currentRoom = null;
        sessionContext = null;
    }
}
